﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Luna.Framework;
using System.Drawing;
using System.Threading;
using Luna;
using VulnDBExplorer.Services;

namespace VulnDBExplorer.Framework.Impl {

	public enum ActionExecutionMode {
		CallerThread, SeparatedThread, WorkpoolThread
	}

	public class Actionable: IActionable {

		public Actionable() {
		}

		public string Name {
			get;
			set;
		}

		public ActionExecutionMode ThreadMode {
			get;
			set;
		}

		public virtual IProgressMonitor ProgressMonitor {
			get;
			protected set;
		}

		public IAppContext Context { get; set; }

		private IActionManager am;
		public IActionManager ActionManager {
			get { return (am == null && Context != null) ? (am = Context.getService<IActionManager>()) : am; }
		}

		/// <summary>
		/// Return the thread that executes action body if ThreadMode is 
		/// <see cref="ActionExecutionMode.SeparatedThread">ActionExecutionMode.SeparatedThread</see> 
		/// </summary>
		public Thread WorkingThread {
			get;
			private set;
		}

		void IActionable.execute(object sender, ActionExecutionArgs args) {
			try {
				if (ActionManager != null)
					ActionManager.WorkingSet.addAction(this);

				switch (ThreadMode) {
					case ActionExecutionMode.CallerThread:
						execute(sender, args);
						break;
					case ActionExecutionMode.WorkpoolThread:
						ThreadPool.QueueUserWorkItem(threadCaller, new object[] { sender, args });
						break;
					case ActionExecutionMode.SeparatedThread:
						WorkingThread = new Thread(threadCaller);
						WorkingThread.Start(new object[] { sender, args });
						break;
				}
			}
			catch (Exception ex) {
				onError(ex);
			}
		}

		public void abort(object stateInfo) {
			if (ThreadMode == ActionExecutionMode.SeparatedThread && WorkingThread != null)
				WorkingThread.Abort(stateInfo);
		}

		private void execute(object sender, ActionExecutionArgs args) {
			try {
				onExecute(sender, args);
				if (Execute != null)
					Execute(sender, args);

				if (Executed != null)
					Executed(sender, args);
			}
			catch (Exception exception) {
				onError(exception);
			}
			finally {
				if (ActionManager != null)
					ActionManager.WorkingSet.removeAction(this);
			}
		}

		protected virtual void onExecute(object sender, ActionExecutionArgs args) {
			// should be done in overrider
		}

		protected virtual void onError(Exception exception) {
			ActionExecuteErrorArgs args = null;
			try {
				if (Error != null) {
					args = new ActionExecuteErrorArgs(exception);
					Error(this, args);
				}
				if (exception is ThreadAbortException)
					if ((exception as ThreadAbortException).ExceptionState is Exception)
						exception = (Exception)(exception as ThreadAbortException).ExceptionState;
					else 
						onAbort();

				if (!(exception is ThreadAbortException) && (args == null || !args.Handled)) {
					IConsole console = Context.getService<IConsole>();
					if (console != null) {
						console.log("Exception occurred.");
						dumpStacktrace(exception, console);
					}
				}
			}
			finally {
				if (ProgressMonitor != null)
					ProgressMonitor.stop();
			}
		}

		protected virtual void onAbort() {
			IConsole console = Context.getService<IConsole>();
			if (console != null)
				console.log("User aborted");
		}

		protected virtual void dumpStacktrace(Exception exception, IConsole console) {
			console.writeLine("Stack trace");
			while (exception != null) {
				console.writeLine("({0}) {1}", exception.GetType().Name, exception.Message);
				console.writeLine(exception.StackTrace);
				exception = exception.InnerException;
			}
		}

		private void threadCaller(object status) {
			object[] args = (object[]) status;
			execute(args[0], args[1] as ActionExecutionArgs);
		}

		public override string ToString() {
			return "{0}, name:{1}, mode:{2}".format(GetType().Name, Name, ThreadMode);
		}

		public event EventHandler<ActionExecutionArgs> Execute;

		public event EventHandler Executed;

		public event EventHandler<ActionExecuteErrorArgs> Error;
	}

	public class VisualActionable : Actionable, IVisualActionable {

		private string text;
		private Image image;
		private bool enabled;

		public VisualActionable() {
			enabled = true;
		}

		public string Text {
			get {
				return text;
			}
			set {
				if (text != value) {
					text = value;
					if (TextChanged != null)
						TextChanged(this, EventArgs.Empty);
				}
			}
		}

		public System.Drawing.Image Image {
			get {
				return image;
			}
			set {
				if (image != value) {
					image = value;
					if (ImageChanged != null)
						ImageChanged(this, EventArgs.Empty);
				}
			}
		}

		public bool Enabled {
			get {
				return enabled;
			}
			set {
				if (enabled != value) {
					enabled = value;
					if (EnableChanged != null)
						EnableChanged(this, EventArgs.Empty);
				}
			}
		}

		public System.Drawing.Image getImage() {
			return image;
		}

		public event EventHandler TextChanged;

		public event EventHandler ImageChanged;

		public event EventHandler EnableChanged;
	}

}
